package metamodel.transformer;

import metamodel.*;
import metamodel.transformer.PropertyValue;
import metamodel.transformer.BottomUpTreeTransformer;

import java.util.*;

/**
 * User: felix
 * Date: 09.06.2005
 */
public class BottomUpTransformingTreeTraverser {

    private ClassNode classNode;
    private BottomUpTreeTransformer visitor;

    public BottomUpTransformingTreeTraverser(ClassNode classNode, BottomUpTreeTransformer visitor){
        this.classNode = classNode;
        this.visitor = visitor;
    }

    public Object transform(Object data){
        final Object tranformed = typeCase(classNode, data);
        resolveCycles();
        return tranformed;
    }

    protected void resolveCycles() {
        // not needed for trees
    }

    private Object  typeCase(ClassNode classNode,Object data) {
        visit(data);
        if (classNode.isPoly()){
            return recursiveTypeNode(classNode, data);
        } else {
            return iterateRecord(classNode, data);
        }
    }

    private Object recursiveTypeNode(ClassNode classNode, Object data) {
        ClassNode concreteClassNode = classNode.getPoly().getClazzForConcreteTypeName(data.getClass().getName());
        if (concreteClassNode.isPoly()){
            return recursiveTypeNode(concreteClassNode, data);
        } else {
            return iterateRecord(concreteClassNode, data);
        }
    }

    protected void visit(Object object) {
           //nothing here
    }

    private Object iterateRecord(ClassNode classNode, Object originalObject) {

        List<PropertyValue> values= new ArrayList<PropertyValue>(classNode.getProperties().size());
        for (Iterator iterator = classNode.getProperties().iterator(); iterator.hasNext();) {
            Property p =  (Property) iterator.next();
            values.add(new PropertyValue(p, iterateProperty(p, originalObject)));
        }
        final Object mappedObject = visitor.mapObject(classNode, values, originalObject);
        mappedObject(originalObject, mappedObject);
        return mappedObject;
    }

    protected void mappedObject(Object originalObject, Object mappedObject) {
        //not needed for mapping of trees.
    }

    private Object iterateProperty(Property p, Object parentObject) {

        final Object propValue = p.getValue(parentObject);
        if (propValue==null){
            return null; //leaf node
        }

        if (p instanceof FlatProperty){
            FlatProperty flatProp=(FlatProperty) p;
            return visitor.mapFlatProperty(flatProp,propValue);
        }

        if (checkCycling(p, parentObject,propValue))
            return null;//has to be resolved later on...

        if (p instanceof ObjectReference){
            ObjectReference objRef = (ObjectReference) p;
            return typeCase(objRef.getElementType(),propValue);
        }

        if (p instanceof CollectionRef){
            CollectionRef collRef=(CollectionRef) p;
            Collection c=(Collection) propValue;
            return visitor.mapCollection(collRef,iterateObjectCollection(collRef, c), c);//iterateObjectCollection(c, p),parent);

        }
        throw new RuntimeException("Unknown meta  type");
    }

    protected boolean checkCycling(Property meta, Object parentObject, Object propValue) {
        return false;
    }


    private Collection iterateObjectCollection(CollectionRef collRef, Collection data) {
        final List transformedElements = new ArrayList(data.size());
        for (Object collectionElement : data) {
            transformedElements.add(typeCase(collRef.getElementType(), collectionElement));
        }
        return transformedElements;
    }
}